package com.globo.opensocial;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * Contains information about a OpenSocial Application.
 *
 * @author falcao
 */
public class Application {

    private String id;
    private String title;
    private String description;
    private String thumbnail;
    private String host;
    private String output;
    private String charset;
    private String outputTest;

    private int height;

    private boolean debug = false;
    private boolean singleton = false;

    /* main app source and code */
    private JavaScript code;
    private File source;

    private List<String> js;
	private List<String> externalJs;  
    private List<String> require;
    private List<String> css;
    private List<String> include;
    private List<String> templates;
    private boolean verbose;

    static private Application instance;

    public Application() throws Exception {
        if (instance != null) {
            throw new Exception("Application already exists");
        }
        instance = this;
        require = new ArrayList<String>();
        css = new ArrayList<String>();
        include = new ArrayList<String>();
        templates = new ArrayList<String>();
        js = new ArrayList<String>();
		externalJs = new ArrayList<String>();
    }

    /**
     * Returns the single instance
     *
     * @return
     */
    static public Application getInstance() {
        return instance;
    }

    static public void clearInstance() {
        instance = null;
    }

    /**
     * Add a css file to be included in application
     *
     * @param fileName
     */
    public void addCss(String fileName) {
        css.add(fileName);
    }

    public void addInclude(String value) {
        include.add(value);
    }

	/**
	 * Adds a js file to be include in the application.
	 * @param fileName
	 */
	public void addExternalJavaScript(String url) {
		
		externalJs.add(url);
	}	
	
	public List<String> getExternalJavaScripts()
	{
		return externalJs;		
	}	

    /**
     * Adds a js file to be include in the application.
     *
     * @param fileName
     */
    public void addJavaScript(String fileName) {
        js.add(fileName);
    }

    public List<String> getJavaScripts() {
        return js;
    }

    /**
     * Adds a template path
     *
     * @param templatePath
     */
    public void addTemplate(String templatePath) {
        templates.add(templatePath);
    }

    /**
     * Set the output file.
     *
     * @param fileName
     */
    public void setOutput(String fileName) {
        output = fileName;
    }

    /**
     * Add a required package to application.
     *
     * @param required
     */
    public void addRequire(String required) {
        require.add(required);
    }

    public void build() throws FileNotFoundException {
        if (!source.isFile()) {
            System.err.println("[ERRO] Main code?");
        }
        code = new JavaScript(this, source);
    }

    public File getAbsolutePath(String fileName) {
        return new File(source.getParentFile(), fileName);
    }

    public String getChaset() {
        return charset;
    }

    public List<String> getCss() {
        return css;
    }

    public String getDescription() {
        return description;
    }

    public int getHeight() {
        return height;
    }

    public String getHost() {
        if (host.endsWith("/")) {
            return host;
        } else {
            return host + "/";
        }
    }

    public String getId() {
        return id;
    }

    /**
     * Returns image path with
     *
     * @return
     */
    public String getImagePath() {
        return getHost() + "resources/";
    }

    public List<String> getInclude() {
        return include;

    }

    public List<String> getRequire() {
        return require;
    }

    public String getThumbnail() {
        return thumbnail;
    }

    public String getTitle() {

        return title;
    }

    public boolean hasCss() {
        return (css.size() > 0);

    }

    public boolean isDebug() {
        return debug;
    }

    public boolean isSingleton() {
        return singleton;
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }

    /**
     * Set the main javascript code.
     *
     * @param file
     */
    public void setCode(File file) {
        source = file;
    }

    /**
     * Set debug
     *
     * @param debug
     */
    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public void setId(String id) {
        this.id = id;
    }

    public void setSingleton(boolean singleton) {
        this.singleton = singleton;
    }

    public void setThumbnail(String thumbnail) {
        this.thumbnail = thumbnail;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * Write to default path.
     *
     * @throws IOException
     */
    public void write() throws IOException {

        File output = null;

        if (this.output == null) {
            this.output = "app.xml";
            output = new File(source.getParentFile(), this.output);
        } else {
            output = new File(this.output);
            if (output.isDirectory()) {
                String source = this.source.getName();
                String name = source.substring(0, source.indexOf("."));
                output = new File(this.output, name + ".xml");
            }
        }

        Writer out = new OutputStreamWriter(new FileOutputStream(output),
                charset);

        write(out);

        out.flush();
    }

    /**
     * Write to default path.
     *
     * @throws IOException
     */
    public void writeTests() throws IOException {

        if (this.outputTest == null) {
            this.outputTest = "tests.htm";
        }

        File output = new File(source.getParentFile(), this.outputTest);

        Writer out = new OutputStreamWriter(new FileOutputStream(output),
                charset);

        // TODO: add to contents.
        String file = "/com/globo/opensocial/tests_template.htm";
        InputStream u = this.getClass().getResourceAsStream(file);
        BufferedReader d = new BufferedReader(new InputStreamReader(u,
                getChaset()));
        StringBuffer buffer = new StringBuffer();
        String line;
        while ((line = d.readLine()) != null) {
            buffer.append(line);
            buffer.append("\n");
        }
        String contents = buffer.toString();

        JavaScript tests = new JavaScript(this, getAbsolutePath("tests.js"));

        // Add <head> code
        Writer head = new StringWriter();
        writeCssInclude(head);
        tests.writeCode(head);
        writeDefaultJavaScript(head);
        writeTemplates(head);

        head.flush();

        contents = contents.replace("#HEAD#", head.toString());

        // Add select fields.
        StringBuffer select = new StringBuffer();
        for (File template : getTemplates()) {
            String name = Util.getMethodName(template.getName(), "get");
            select.append("<option>" + name + "</option>");
        }

        contents = contents.replace("#SELECT#", select.toString());

        out.append(contents);
        out.flush();
    }

    /**
     * Write application
     *
     * @param os
     * @throws IOException
     */
    public void write(Writer os) throws IOException {
        writeHeader(os);
        writeContent(os);
        writeFooter(os);
    }

    /**
     * @throws IOException
     */
    private void writeContent(Writer os) throws IOException {
        os.append("<Content type=\"html\"><![CDATA[\n");
        info("Writing CSS");
        writeCss(os);
        os.append("\n");
        info("Writing External JavaScripts");
        writeExternalJavaScripts(os);
        info("Writing JavaScripts");
        writeJavaScripts(os);
        os.append("\n");
        info("Writing templates");
        writeTemplates(os);
        os.append("<div id=\"app__MODULE_ID__\"></div>\n");
        os.append("]]></Content>\n");
    }

	
	private void writeExternalJavaScripts(Writer os) throws IOException  {
		// TODO Auto-generated method stub
		for (String url : getExternalJavaScripts()) {
			os.append("<script type=\"text/javascript\" src=\"" + url + "\" ></script> ");
		}
	}

    /**
     * Write all js in the same script tag.
     *
     * @param out
     * @throws IOException
     */
    private void writeJavaScripts(Writer out) throws IOException {

        JavaScript.writeOpenScript(out);


        info("Writing Main Code");
        code.writeJs(out);
        out.append("\n");


        info("Writing Extra JS files");
        for (String jsFile : getJavaScripts()) {
            new JavaScript(this, getAbsolutePath(jsFile)).writeJs(out);
        }
        out.append("\n");

        // writeDefaultJavaScript(out);

        String file = "/com/globo/opensocial/default.js";
        InputStream inputStream = this.getClass().getResourceAsStream(file);

        new JavaScript(this, inputStream).writeJs(out);

        JavaScript.writeCloseScript(out);

    }

    private void writeDefaultJavaScript(Writer out) throws IOException {

        info("Writing Embedded Resource default.js");
        String file = "/com/globo/opensocial/default.js";
        InputStream inputStream = this.getClass().getResourceAsStream(file);

        new JavaScript(this, inputStream).writeCode(out);
    }

    private void writeCssInclude(Writer out) throws IOException {
        Css.writeOpenTag(out);
        if (hasCss()) {
            for (String cssFile : getCss()) {
                out.append("@import \"resources/"
                        + getAbsolutePath(cssFile).getName() + "\";\n");
            }
        } else {
            final String defaultCss = "resources/default.css";
            // logger.debug(getAbsolutePath(defaultCss));
            if (getAbsolutePath(defaultCss).isFile()) {
                out.append("@import \"resources/"
                        + getAbsolutePath(defaultCss).getName() + "\";\n");
            }
        }
        Css.writeCloseTag(out);
    }

    /**
     * Write the CSS Code.
     *
     * @param out
     * @throws IOException
     */
    private void writeCss(Writer out) throws IOException {
        // Ok. Now write the code!.
        Css.writeOpenTag(out);
        if (hasCss()) {
            for (String cssFile : getCss()) {
                new Css(this, getAbsolutePath(cssFile)).writeCode(out);
            }
        } else {
            final String defaultCss = "resources/default.css";
            // logger.debug(getAbsolutePath(defaultCss));
            if (getAbsolutePath(defaultCss).isFile()) {
                new Css(this, getAbsolutePath(defaultCss)).writeCode(out);
            }
        }
        Css.writeCloseTag(out);
    }

    /**
     * | Write the Module Footer.
     *
     * @param os
     * @throws IOException
     */
    private void writeFooter(Writer os) throws IOException {
        os.append("</Module>\n");
    }

    /**
     * @param os
     * @throws IOException
     */
    public void writeHeader(Writer os) throws IOException {

        info("Writing header");


        StringBuffer buffer = new StringBuffer();

        buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
        buffer.append("<Module>\n");
        buffer.append("<ModulePrefs ");

        buffer.append("title=\"" + getTitle() + "\" ");
        buffer.append("height=\"" + getHeight() + "\" ");
        buffer.append("description=\"" + getDescription() + "\" ");
        if (isSingleton()) {
            buffer.append("singleton=\"true\" ");
        }

        if (getThumbnail() == null) {
            buffer.append("thumbnail=\"" + getHost()
                    + "resources/thumbnail.jpg\" ");
        } else {
            buffer.append("thumbnail=\"" + getHost() + getThumbnail() + "\" ");
        }
        buffer.append(">\n");

        // Required modules.
        for (String r : getRequire()) {
            buffer.append("<Require feature=\"" + r + "\"/>\n");
        }

        buffer.append("</ModulePrefs>\n");

        // add userpref.xml (<UserPref>)
        File config = getAbsolutePath("/userpref.xml");
        if (config.isFile()) {
            buffer.append("\n");
            buffer.append(new Content(this, config).getContent());
            buffer.append("\n");
        }
        os.append(buffer.toString());
    }

    private List<File> getTemplates() {
        ArrayList<File> list = new ArrayList<File>();

        for (String templateDir : this.templates) {

            File templates = getAbsolutePath(templateDir);
            if (!templates.isDirectory()) {
                continue;
            }

            File[] files = templates.listFiles();

            // sort files
            java.util.Arrays.sort(files, new Comparator<File>() {
                public int compare(File o1, File o2) {
                    return o1.getName().compareTo(o2.getName());
                }
            });

            for (File file : files) {
                if (!file.isFile() || file.getName().startsWith(".")) {
                    continue;
                }
                list.add(file);
            }
        }
        return list;
    }

    private void writeTemplates(Writer out) throws IOException {
        JavaScript.writeOpenScript(out);
        for (File file : getTemplates()) {
            Template t = new Template(this, file.getAbsoluteFile());
            t.writeCode(out);
        }
        JavaScript.writeCloseScript(out);
    }

    public void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }

    public void debug(String string) {
        System.out.println("[DEBUG] " + string);
    }

    public void info(String string) {
		if (verbose)
			System.out.println("[INFO] " + string);
	}

	public void error(int line, String string) {
		System.out.println("[ERROR," + line + "] " + string);
	}

	
}
